/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.dex.code;

import java.util.HashSet;

import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.type.Type;

/**
 * Container for all the pieces of a concrete method. Each instance corresponds
 * to a {@code code} structure in a {@code .dex} file.
 */
public final class DalvCode {

	/**
	 * how much position info to preserve; one of the static constants in
	 * {@link PositionList}
	 */
	private final int positionInfo;

	/**
	 * {@code null-ok;} the instruction list, ready for final processing; nulled
	 * out in {@link #finishProcessingIfNecessary}
	 */
	private OutputFinisher unprocessedInsns;

	/**
	 * {@code non-null;} unprocessed catch table; nulled out in
	 * {@link #finishProcessingIfNecessary}
	 */
	private CatchBuilder unprocessedCatches;

	/**
	 * {@code null-ok;} catch table; set in {@link #finishProcessingIfNecessary}
	 */
	private CatchTable catches;

	/**
	 * {@code null-ok;} source positions list; set in
	 * {@link #finishProcessingIfNecessary}
	 */
	private PositionList positions;

	/**
	 * {@code null-ok;} local variable list; set in
	 * {@link #finishProcessingIfNecessary}
	 */
	private LocalList locals;

	/**
	 * {@code null-ok;} the processed instruction list; set in
	 * {@link #finishProcessingIfNecessary}
	 */
	private DalvInsnList insns;

	/**
	 * Constructs an instance.
	 * 
	 * @param positionInfo
	 *            how much position info to preserve; one of the static
	 *            constants in {@link PositionList}
	 * @param unprocessedInsns
	 *            {@code non-null;} the instruction list, ready for final
	 *            processing
	 * @param unprocessedCatches
	 *            {@code non-null;} unprocessed catch (exception handler) table
	 */
	public DalvCode(int positionInfo, OutputFinisher unprocessedInsns,
			CatchBuilder unprocessedCatches) {
		if (unprocessedInsns == null) {
			throw new NullPointerException("unprocessedInsns == null");
		}

		if (unprocessedCatches == null) {
			throw new NullPointerException("unprocessedCatches == null");
		}

		this.positionInfo = positionInfo;
		this.unprocessedInsns = unprocessedInsns;
		this.unprocessedCatches = unprocessedCatches;
		this.catches = null;
		this.positions = null;
		this.locals = null;
		this.insns = null;
	}

	/**
	 * Finish up processing of the method.
	 */
	private void finishProcessingIfNecessary() {
		if (insns != null) {
			return;
		}

		insns = unprocessedInsns.finishProcessingAndGetList();
		positions = PositionList.make(insns, positionInfo);
		locals = LocalList.make(insns);
		catches = unprocessedCatches.build();

		// Let them be gc'ed.
		unprocessedInsns = null;
		unprocessedCatches = null;
	}

	/**
	 * Assign indices in all instructions that need them, using the given
	 * callback to perform lookups. This must be called before {@link #getInsns}
	 * .
	 * 
	 * @param callback
	 *            {@code non-null;} callback object
	 */
	public void assignIndices(AssignIndicesCallback callback) {
		unprocessedInsns.assignIndices(callback);
	}

	/**
	 * Gets whether this instance has any position data to represent.
	 * 
	 * @return {@code true} iff this instance has any position data to represent
	 */
	public boolean hasPositions() {
		return (positionInfo != PositionList.NONE)
				&& unprocessedInsns.hasAnyPositionInfo();
	}

	/**
	 * Gets whether this instance has any local variable data to represent.
	 * 
	 * @return {@code true} iff this instance has any local variable data to
	 *         represent
	 */
	public boolean hasLocals() {
		return unprocessedInsns.hasAnyLocalInfo();
	}

	/**
	 * Gets whether this instance has any catches at all (either typed or
	 * catch-all).
	 * 
	 * @return whether this instance has any catches at all
	 */
	public boolean hasAnyCatches() {
		return unprocessedCatches.hasAnyCatches();
	}

	/**
	 * Gets the set of catch types handled anywhere in the code.
	 * 
	 * @return {@code non-null;} the set of catch types
	 */
	public HashSet<Type> getCatchTypes() {
		return unprocessedCatches.getCatchTypes();
	}

	/**
	 * Gets the set of all constants referred to by instructions in the code.
	 * 
	 * @return {@code non-null;} the set of constants
	 */
	public HashSet<Constant> getInsnConstants() {
		return unprocessedInsns.getAllConstants();
	}

	/**
	 * Gets the list of instructions.
	 * 
	 * @return {@code non-null;} the instruction list
	 */
	public DalvInsnList getInsns() {
		finishProcessingIfNecessary();
		return insns;
	}

	/**
	 * Gets the catch (exception handler) table.
	 * 
	 * @return {@code non-null;} the catch table
	 */
	public CatchTable getCatches() {
		finishProcessingIfNecessary();
		return catches;
	}

	/**
	 * Gets the source positions list.
	 * 
	 * @return {@code non-null;} the source positions list
	 */
	public PositionList getPositions() {
		finishProcessingIfNecessary();
		return positions;
	}

	/**
	 * Gets the source positions list.
	 * 
	 * @return {@code non-null;} the source positions list
	 */
	public LocalList getLocals() {
		finishProcessingIfNecessary();
		return locals;
	}

	/**
	 * Class used as a callback for {@link #assignIndices}.
	 */
	public static interface AssignIndicesCallback {

		/**
		 * Gets the index for the given constant.
		 * 
		 * @param cst
		 *            {@code non-null;} the constant
		 * @return {@code >= -1;} the index or {@code -1} if the constant
		 *         shouldn't actually be reified with an index
		 */
		public int getIndex(Constant cst);
	}
}
